#pragma once

#include<algorithm>
#include<mutex>
#include<unordered_map>
#include<fstream>
#include<cassert>
#include<cstdint>
#include<jsoncpp/json/json.h>

#include"view.hpp"
#include"model_file.hpp"
#include"../HttpServer/Client.hpp"
const std::string machine_path = "/home/dyx/Oline_judge/oj_server/Machine_Config/all_machines.conf"; 

//机器类，用于描述多台机器的信息，便于oj_server管理多台机器，进行负载均衡。
struct Machine{
    private:
        //临界资源，需要加锁访问
        uint64_t _load_num; //当前机器负载的数量
    public:
        std::string _ip;
        std::string _port;
        std::mutex *_mutex; //同一时刻，可能有多条请求，需要加锁来顺序处理。（这里用指针，是因为cpp禁止拷贝锁，所以用指针来共享锁）
        std::string name; //机器名称(设置为ip:port)
        Machine(): _ip(""), _port(""), _load_num(0){};
        ~Machine(){};
        void Add_Load()
        {
            if(_mutex) _mutex->lock();
            _load_num++;
            if(_mutex) _mutex->unlock();
        }
        void Sub_Load()
        {
            if(_mutex) _mutex->lock();
            _load_num--;
            if(_mutex) _mutex->unlock();
        }
        //机器负载清零
        void Clear_Load()
        {
            if(_mutex) _mutex->lock();
            _load_num = 0;
            if(_mutex) _mutex->unlock();
        }
        //获取机器负载数量
        uint64_t Get_Load_Num()
        {
            uint64_t load_num = 0;
            if(_mutex) _mutex->lock();
            load_num = _load_num;
            if(_mutex) _mutex->unlock();    
            return load_num;
        }

};

//负载均衡工具类，用于对多台机器进行负载均衡。
//需要先加载多个机器信息
class Util_LoadBalance{
    private:
        std::unordered_map<std::string, Machine> _machines; //机器信息
        std::unordered_map<std::string, Machine> _online_machines; //在线机器信息
        std::unordered_map<std::string, Machine> _offline_machines; //离线机器信息
        std::mutex _mutex; //全局锁，用于对机器信息的访问同步
    public:
        Util_LoadBalance(){ assert(Load_Machines_Config(machine_path));}
        ~Util_LoadBalance(){}
        bool Load_Machines_Config(const std::string &machine_path)
        {
            std::ifstream ifs(machine_path);
            if(!ifs.is_open()){
                LOG__ERR("打开配置文件失败:%s", machine_path.c_str());
                ifs.close();
                return false;
            }

            std::string line;
            while(std::getline(ifs, line)){
                std::vector<std::string> tokens;

                Util_String::SplitStr(line, &tokens,":");
                if(tokens.size() != 2){
                    LOG__ERR("配置文件格式错误:%s", line.c_str());
                    continue;
                }
                Machine machine;
                machine._ip = tokens[0];
                machine._port = tokens[1];
                machine._mutex = new std::mutex();//每一个机器都有自己的锁
                std::string ip_port = tokens[0] + ":" + tokens[1];
                machine.name = ip_port;

                _machines[ip_port] = machine;
                _online_machines[ip_port] = machine;
            }
            ifs.close();
            LOG__INF("加载机器配置成功");
            return true;
        }

        bool Select_Machine(Machine **machine)
        {
            _mutex.lock();//加锁

            if(_online_machines.empty()){
                _mutex.unlock();
                LOG__ERR("没有可用的机器");
                return false;
            }
            uint64_t min_load = UINT64_MAX;
            for(auto &it:_online_machines){
                if(it.second.Get_Load_Num()< min_load){
                    min_load = it.second.Get_Load_Num();
                    *machine = &it.second;//找到最小负载的机器的地址，并将其放入machine二级指针中，（地址传参便于同步负载信息）
                }
            }
            _mutex.unlock();
            return true;
        }

        void Set_All_Machines_Online()
        {
            _mutex.lock();
            _online_machines.insert(_machines.begin(), _machines.end());
            _offline_machines.clear();
            _mutex.unlock();
            LOG__INF("所有机器上线");
        }

        //设置某台机器下线
        void Set_One_Machine_Offline(const std::string &ip_port)
        {
            _mutex.lock();
            auto it = _online_machines.find(ip_port);
            if(it != _online_machines.end()){
                
                _offline_machines[ip_port] = it->second;//加入离线
                _offline_machines[ip_port].Clear_Load();//这个函数内的锁是Machine的锁，不是LoadBalance的锁，所以不会死锁
                _online_machines.erase(it);//移除在线
                LOG__INF("机器下线:%s", ip_port.c_str());
            }
            _mutex.unlock();
        }
        void Show_All_Online_Machines()
        {
            _mutex.lock();
            for(auto &it:_online_machines){
                LOG__INF("在线机器:%s 负载:%lu", it.first.c_str(), it.second.Get_Load_Num());
            }
            _mutex.unlock();
        }
};


//将题目信息导入渲染模板（view模块），整合前面所有的功能模块，形成完整的控制模块。
class Control{
    private:
        Model _model;//题目信息模型
        View _view;//渲染模板
        Util_LoadBalance _load_balance;//负载均衡工具类
    public:
        bool Get_All_Questions_Html(std::string *html)
        {
            std::vector<Question> questions;
            if(_model.GetAllQuestions(&questions)){

                sort(questions.begin(), questions.end(), [](const Question& a, const Question& b){
                    return std::atoi(a.number.c_str()) < std::atoi(b.number.c_str());
                });
                
                _view.Build_All_Question_Html(questions, html);
                
                return true;
            }else{
                
                *html = "获取题目列表信息失败"; 
                return false;
            }
        
        }

        bool Get_One_Question_Html(const std::string &number, std::string *html)
        {
            Question question;
            if(_model.GetOneQuestion(number, &question)){

                _view.Build_One_Question_Html(question, html);
                return true;

            }else{
                *html = "题目不存在"; 
                return false;
            }
        }
        void Set_All_Machines_Online()
        {
            _load_balance.Set_All_Machines_Online();
        }
        //提交题目，并进行判题
        //根据题目编号获取信息，并进行判题，返回判题结果。
        //in_json是客户提交的代码，out_json是判题结果。
        void Judge(const std::string &number ,const std::string &in_json, std::string *out_json)
        {
            Question question;
            _model.GetOneQuestion(number, &question);

            Json::Value in_value;
            Json::Reader reader;
            reader.parse(in_json, in_value);//反序列化
            std::string code = in_value["code"].asString();//获取用过户端提交的代码

            //交给编译服务器编译的代码
            Json::Value compile_value;
            compile_value["input"] = in_value["input"].asString();
            compile_value["code"] = code + "\n" + question.test;
            compile_value["cpu_limit"] = question.cpu_limit;
            compile_value["mem_limit"] = question.mem_limit;
            Json::FastWriter writer;
            std::string compile_json = writer.write(compile_value);//将用户提交的代码和测试用例拼接成完整代码序列化

            while(true){
                Machine *machine;//获得机器地址，便于同步负载信息
                if(!_load_balance.Select_Machine(&machine)){
                    //没有可用的机器
                    break;
                }
                std::cout<<"选择机器:"<<machine->name<<std::endl;
                HttpClient client(machine->_ip,std::atoi(machine->_port.c_str()));//创建客户端连接
                machine->Add_Load();//增加负载
                auto resp = client.Post("/compile_run", compile_json, "application/json");//发送编译请求
                if(resp != nullptr){
                    if(resp->_status == 200){//响应成功
                        *out_json = resp->_body;
                        machine->Sub_Load();//减少负载
                        LOG__INF("编译运行服务成功");
                        break;
                    }else{
                        machine->Sub_Load();//减少负载
                        LOG__ERR("编译运行服务失败:%s 响应状态码%d", resp->_body.c_str(), resp->_status);
                        break;
                    }
                }else{
                    LOG__ERR("编译运行服务失败:连接失败 主机: %s 已经离线", machine->name.c_str());
                    _load_balance.Set_One_Machine_Offline(machine->name);//设置机器下线(内部会清空负载)
                    LOG__DBG("-----------");
                    _load_balance.Show_All_Online_Machines();
                    LOG__DBG("-----------");
                    continue;
                }
                
                
            }    
        }

};
